package controller;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import model.MoodleCategory;
import model.MoodleException;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import service.MoodleRestWSConfigSingleton;
import service.MoodleRestWSConnector;
import util.MoodleCategoryToDelete;

/**
 * Category controller with Moodle category function implementation.
 *
 * @author Gennady Kolomoets
 */
public class MoodleCategoryWS {

    private final JSONParser parser = new JSONParser();

    /**
     * Universal method of creating categories.
     *
     * @param newCategories set of the categories that will be created
     * @return set of created categories identified by their id and name
     * @throws model.MoodleException if response of the {@code moodleWSRestCall} method is Moodle exception
     * @throws org.json.simple.parser.ParseException while parsing of Moodle JSON-response detects error
     *
     * @see MoodleRestWSConnector
     */
    public Set<MoodleCategory> createCategories(Set<MoodleCategory> newCategories) throws MoodleException, ParseException {

        MoodleRestWSConfigSingleton config = MoodleRestWSConfigSingleton.getInstance();

        String domainName = config.getDomainName();
        String token = config.getToken();
        String restformat = config.getRestFormat();
        //Web-service's function name
        String functionName = "core_course_create_categories";

        StringBuilder urlParameters = new StringBuilder();
        int i = 0;
        for (MoodleCategory category : newCategories) {
            try {
                urlParameters.append("&categories[").append(i).append("][name]=").append(URLEncoder.encode(category.getName(), "UTF-8"))
                        .append("&categories[").append(i).append("][parent]=").append(category.getParent())
                        .append("&categories[").append(i).append("][idnumber]=").append(URLEncoder.encode(category.getIdnumber(), "UTF-8"))
                        .append("&categories[").append(i).append("][description]=").append(URLEncoder.encode(category.getDescription(), "UTF-8"))
                        .append("&categories[").append(i).append("][descriptionformat]=").append(category.getDescriptionFormat())
                        .append("&categories[").append(i).append("][theme]=").append(URLEncoder.encode(category.getTheme(), "UTF-8"));
            } catch (UnsupportedEncodingException ex) {
                Logger.getLogger(MoodleCategoryWS.class
                        .getName()).log(Level.SEVERE, null, ex);
            }
            i++;
        }

        //Request to Web-service's function invoke
        String response = MoodleRestWSConnector.moodleWSRestCall(restformat, domainName, token, functionName, urlParameters.toString());

        Set<MoodleCategory> categories = new HashSet<MoodleCategory>();
        //Parsing of JSON-response
        Object obj = parser.parse(response);
        JSONArray array = (JSONArray) obj;
        //Fore some id it was empty array
        if (array.size() != 0) {
            for (Object arrayItem : array) {
                JSONObject jsonObj = (JSONObject) arrayItem;
                long id = (long) jsonObj.get("id");
                String name = jsonObj.get("name").toString();
                MoodleCategory category = new MoodleCategory(id, name);
                categories.add(category);
            }
        }

        return categories;
    }

    /**
     * Creates a single category (supplementary method).
     *
     * @param newCategory category to be created
     * @return created {@code MoodleCategory} instance
     * @throws model.MoodleException if {@code createCategories} method throws {@code MoodleException}
     * @throws org.json.simple.parser.ParseException if {@code createCategories} method throws {@code ParseException}
     */
    public MoodleCategory createCategory(MoodleCategory newCategory) throws MoodleException, ParseException {
        Set<MoodleCategory> newCategories = new HashSet<MoodleCategory>();
        newCategories.add(newCategory);
        Set<MoodleCategory> createdCategories = createCategories(newCategories);
        MoodleCategory createdCategory = createdCategories.iterator().next();
        return createdCategory;
    }

    /**
     * Universal method of getting of categories.
     *
     * @param criteria String key-value pairs restricted returnedcategories set. Expected keys are: "id", "name",
     * "parent", "idnumber", "visible", "theme" If the key is not passed, then the method return all categories that the
     * user can see. There is no possibility to add several key-values pair with the same key value, fjr example with
     * several id-id-values pairs. The method can return several categories with some key values matched criteria, for
     * example - return all subcategory for some category with parent=some parent id.
     * @param addSubcategories 0 - get categories without subcategories, 1 - get vategories with subcategories
     * @return a set of categories in accordance with the criteria and addSubcategories value
     * @throws MoodleException
     * @throws ParseException
     */
    public Set<MoodleCategory> getCategories(Map<String, String> criteria, byte addSubcategories) throws MoodleException, ParseException {

        MoodleRestWSConfigSingleton config = MoodleRestWSConfigSingleton.getInstance();

        String domainName = config.getDomainName();
        String token = config.getToken();
        String restformat = config.getRestFormat();
        //Web-service's function name
        String functionName = "core_course_get_categories";
        StringBuilder urlParameters = new StringBuilder();
        //Empty criteria makes return the full list of categories
        if (criteria.isEmpty()) {
            urlParameters.append("");
        } else {
            int i = 0;
            for (String key : criteria.keySet()) {
                try {
                    urlParameters.append("&criteria[").append(i).append("][key]=").append(key)
                            .append("&criteria[").append(i).append("][value]=").append(URLEncoder.encode(criteria.get(key), "UTF-8"));
                    i++;
                } catch (UnsupportedEncodingException ex) {
                    Logger.getLogger(MoodleCategoryWS.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            urlParameters.append("&addsubcategories=").append(addSubcategories);
        }

        //Request to Web-service's function invoke
        String response = MoodleRestWSConnector.moodleWSRestCall(restformat, domainName, token, functionName, urlParameters.toString());
        /*Use TreeSet for getting categories in sorted order*/
        Set<MoodleCategory> categories = new TreeSet<MoodleCategory>();
        //Parsing of JSON-response
        Object obj = parser.parse(response);
        JSONArray array = (JSONArray) obj;
        //Fore some id it was empty array
        if (array.size() != 0) {
            for (Object arrayItem : array) {
                JSONObject jsonObj = (JSONObject) arrayItem;
                long id = (long) jsonObj.get("id");
                String name = jsonObj.get("name").toString();
                String idnumber = jsonObj.get("idnumber").toString();
                String description = jsonObj.get("description").toString();
                long descriptionFormat = (long) jsonObj.get("descriptionformat");
                long parent = (long) jsonObj.get("parent");
                long sortorder = (long) jsonObj.get("sortorder");
                long coursecount = (long) jsonObj.get("coursecount");
                long visible = (long) jsonObj.get("visible");
                long visibleold = (long) jsonObj.get("visibleold");
                long timemodified = (long) jsonObj.get("timemodified");
                long depth = (long) jsonObj.get("depth");
                String path = jsonObj.get("path").toString();
                String theme = (jsonObj.get("theme") == null) ? "" : jsonObj.get("theme").toString();
                MoodleCategory category = new MoodleCategory(id, name, idnumber, description,
                        descriptionFormat, parent, sortorder, coursecount,
                        visible, visibleold, timemodified, depth, path,
                        theme);
                categories.add(category);
            }
        }

        return categories;
    }

    /**
     * Get all categories with subcategories (supplementary method).
     *
     * @return total set of MoodleCategory instances.
     * @throws MoodleException
     * @throws ParseException
     */
    public Set<MoodleCategory> getAllCategories() throws MoodleException, ParseException {
        Map<String, String> criteria = new HashMap<String, String>();
        byte addSubcategories = 1;   //incclude subcategories
        Set<MoodleCategory> categories = getCategories(criteria, addSubcategories);
        return categories;
    }

    /**
     * Get a single category by category idnumber (supplementary method). Moodle prevents the creation of two categories
     * with the same idnumber.
     *
     * @param categoryIdnumber unique category Idnumber.
     * @return MoodleCategory instance
     * @throws MoodleException
     * @throws ParseException
     */
    public MoodleCategory getCategoryByIdnumber(String categoryIdnumber) throws MoodleException, ParseException {
        Map<String, String> criteria = new HashMap<>();
        /*Get category without subcategories*/
        byte addSubcategories = 0;
        criteria.put("id", categoryIdnumber);
        Set<MoodleCategory> categories = getCategories(criteria, addSubcategories);
        if (categories.size() != 1) {
            new MoodleException("Duplcate", "duplicate idnumbers", "Duplicate idnumber values!");
        }
        MoodleCategory category = categories.iterator().next();
        return category;
    }

    /**
     * Get a single category by category name (supplementary method).
     *
     * @param categoryName unique category name
     * @return MoodleCategory instance
     * @throws MoodleException
     * @throws ParseException
     */
    public MoodleCategory getCategoryByName(String categoryName) throws MoodleException, ParseException {
        Map<String, String> criteria = new HashMap<>();
        /*Get category without subcategories*/
        byte addSubcategories = 0;
        criteria.put("name", categoryName);
        Set<MoodleCategory> categories = getCategories(criteria, addSubcategories);
        if (categories.size() != 1) {
            new MoodleException("Duplcate", "duplicate ids", "Duplicate name values!");
        }
        MoodleCategory category = categories.iterator().next();
        return category;
    }

    /**
     * Get subcategories of parent category (supplementary method).
     *
     * @param parentCategory parent category Id
     * @param addSubcategories 0 - get categories without subcategories, 1 - get vategories with subcategories
     * @return a set of subcategories of parent
     * @throws MoodleException
     * @throws ParseException
     */
    public Set<MoodleCategory> getCategoriesOfParent(long parentCategory, byte addSubcategories) throws MoodleException, ParseException {
        Map<String, String> criteria = new HashMap<>();
        criteria.put("parent", String.valueOf(parentCategory));
        Set<MoodleCategory> categories = getCategories(criteria, addSubcategories);
        return categories;
    }

    /**
     * Universal method for updating of categories.
     *
     * @param updatedCategories set of categories with updated properties
     * @throws MoodleException
     */
    public void updateCategories(Set<MoodleCategory> updatedCategories) throws MoodleException {

        MoodleRestWSConfigSingleton config = MoodleRestWSConfigSingleton.getInstance();

        String domainName = config.getDomainName();
        String token = config.getToken();
        String restformat = config.getRestFormat();
        //Web-service's function name
        String functionName = "core_course_update_categories";

        StringBuilder urlParameters = new StringBuilder();
        int i = 0;
        for (MoodleCategory category : updatedCategories) {
            try {
                urlParameters.append("&categories[").append(i).append("][id]=").append(category.getId())
                        .append("&categories[").append(i).append("][name]=").append(URLEncoder.encode(category.getName(), "UTF-8"))
                        .append("&categories[").append(i).append("][idnumber]=").append(URLEncoder.encode(category.getIdnumber(), "UTF-8"))
                        .append("&categories[").append(i).append("][parent]=").append(category.getParent())
                        .append("&categories[").append(i).append("][description]=").append(URLEncoder.encode(category.getDescription(), "UTF-8"))
                        .append("&categories[").append(i).append("][descriptionformat]=").append(category.getDescriptionFormat())
                        .append("&categories[").append(i).append("][theme]=").append(URLEncoder.encode(category.getTheme(), "UTF-8"));
            } catch (UnsupportedEncodingException ex) {
                Logger.getLogger(MoodleCategoryWS.class
                        .getName()).log(Level.SEVERE, null, ex);
            }
            i++;
        }

        //Request to Web-service's function invoke - returns "null"
        String response = MoodleRestWSConnector.moodleWSRestCall(restformat, domainName, token, functionName, urlParameters.toString());
    }

    /**
     * Updates a sungle category (sipplementary method).
     *
     * @param category MoodleCategory instance with modified field values to be updated
     * @throws MoodleException
     */
    public void updateCategory(MoodleCategory category) throws MoodleException {
        Set<MoodleCategory> updatedCategories = new HashSet<MoodleCategory>();
        updatedCategories.add(category);
        updateCategories(updatedCategories);
    }

    /**
     * Universal method of deleting categories. For non-recursive deleting of deleted category subcategories orphan
     * subcategories move to new parent category.
     *
     * @param categoriesToDelete set of categories will be deleted
     * @throws MoodleException
     */
    public void deleteCategories(Set<MoodleCategoryToDelete> categoriesToDelete) throws MoodleException {

        MoodleRestWSConfigSingleton config = MoodleRestWSConfigSingleton.getInstance();

        String domainName = config.getDomainName();
        String token = config.getToken();
        String restformat = config.getRestFormat();
        //Web-service's function name
        String functionName = "core_course_delete_categories";

        //TODO for MoodleCategoryToDelete instances in categoriesToDelete set perform analysis for deleting of subcategories
        //from the set if in the set there is it parent category and deleting is recursive!!!
        StringBuilder urlParameters = new StringBuilder();
        int i = 0;
        for (MoodleCategoryToDelete categoryToDelete : categoriesToDelete) {
            String recursive = categoryToDelete.isRecursiveDelete() ? "1" : "0";
            if (categoryToDelete.isRecursiveDelete()) {
                urlParameters.append("&categories[").append(i).append("][id]=").append(categoryToDelete.getCategoryToDelete().getId())
                        .append("&categories[").append(i).append("][recursive]=").append(recursive);
            } else {
                urlParameters.append("&categories[").append(i).append("][id]=").append(categoryToDelete.getCategoryToDelete().getId())
                        .append("&categories[").append(i).append("][newparent]=").append(categoryToDelete.getNewParentCategory().getId())
                        .append("&categories[").append(i).append("][recursive]=").append(recursive);
            }
            i++;
        }

        //Request to Web-service's function invoke - returns "null"
        String response = MoodleRestWSConnector.moodleWSRestCall(restformat, domainName, token, functionName, urlParameters.toString());
    }

    /**
     * Universal method for deleting a single category with and without their subcategories.
     *
     * @param categoryToDelete category will be deleted
     * @param isRecursive if {@code true} - even category will be deleted with it subcategories, if {@code false} - even
     * category will be deleted without it subcategories and these subcategory move to newParentCategory
     * @param newParentCategory category for orphan subcategories after category deleting
     * @throws MoodleException
     */
    public void deleteCategory(MoodleCategory categoryToDelete, boolean isRecursive, MoodleCategory newParentCategory) throws MoodleException {

        MoodleRestWSConfigSingleton config = MoodleRestWSConfigSingleton.getInstance();

        String domainName = config.getDomainName();
        String token = config.getToken();
        String restformat = config.getRestFormat();
        //Web-service's function name
        String functionName = "core_course_delete_categories";

        String recursive = isRecursive ? "1" : "0";
        StringBuilder urlParameters = new StringBuilder();
        int i = 0;
        if (isRecursive) {
            urlParameters.append("&categories[").append(i).append("][id]=").append(categoryToDelete.getId())
                    .append("&categories[").append(i).append("][recursive]=").append(recursive);
        } else {
            urlParameters.append("&categories[").append(i).append("][id]=").append(categoryToDelete.getId())
                    .append("&categories[").append(i).append("][newparent]=").append(newParentCategory.getId())
                    .append("&categories[").append(i).append("][recursive]=").append(recursive);
        }

        //Request to Web-service's function invoke - returns "null"
        String response = MoodleRestWSConnector.moodleWSRestCall(restformat, domainName, token, functionName, urlParameters.toString());
    }
}
